// Package config provides global server-wide settings.
package config

import (
	"context"
	"log"
	"net/url"
	"strings"
	"time"

	// TODO: figure out how to properly implement urlx
	// the implementation in 5f8b659b49 causes config.go to segfault due to a nil pointer dereference when the PIXIVFE_IMAGEPROXY env var is not set
	// "github.com/goware/urlx"
	"github.com/sethvargo/go-envconfig"

	"codeberg.org/vnpower/pixivfe/v2/i18n"
	"codeberg.org/vnpower/pixivfe/v2/server/token_manager"
	"codeberg.org/vnpower/pixivfe/v2/server/utils"
)

var GlobalConfig ServerConfig

// REVISION stores the current version's revision information
var REVISION string = ""

const (
	unknownRevision = "unknown"
	revisionFormat  = "date-hash[+dirty]"
	defaultPort     = "8282"
)

type ServerConfig struct {
	Version      string
	Revision     string
	RevisionDate string
	RevisionHash string
	IsDirty      bool
	StartingTime string // used in /about page

	Host string `env:"PIXIVFE_HOST"`

	// One of the two is required
	Port       string `env:"PIXIVFE_PORT"`
	UnixSocket string `env:"PIXIVFE_UNIXSOCKET"`

	RepoURL string `env:"PIXIVFE_REPO_URL,overwrite"` // used in /about page

	Token               []string `env:"PIXIVFE_TOKEN,required"` // may be multiple tokens. delimiter is ','
	TokenManager        *token_manager.TokenManager
	TokenLoadBalancing  string        `env:"PIXIVFE_TOKEN_LOAD_BALANCING,overwrite"`
	TokenMaxRetries     int           `env:"PIXIVFE_TOKEN_MAX_RETRIES,overwrite"`
	TokenBaseTimeout    time.Duration `env:"PIXIVFE_TOKEN_BASE_TIMEOUT,overwrite"`
	TokenMaxBackoffTime time.Duration `env:"PIXIVFE_TOKEN_MAX_BACKOFF_TIME,overwrite"`

	// API request level backoff settings
	APIMaxRetries     int           `env:"PIXIVFE_API_MAX_RETRIES,overwrite"`
	APIBaseTimeout    time.Duration `env:"PIXIVFE_API_BASE_TIMEOUT,overwrite"`
	APIMaxBackoffTime time.Duration `env:"PIXIVFE_API_MAX_BACKOFF_TIME,overwrite"`

	AcceptLanguage string `env:"PIXIVFE_ACCEPTLANGUAGE,overwrite"`
	RequestLimit   uint64 `env:"PIXIVFE_REQUESTLIMIT"` // if 0, request limit is disabled

	ProxyServer_staging string  `env:"PIXIVFE_IMAGEPROXY,overwrite"`
	ProxyServer         url.URL // proxy server URL, may or may not contain authority part of the URL

	ProxyCheckEnabled  bool          `env:"PIXIVFE_PROXY_CHECK_ENABLED,overwrite"`
	ProxyCheckInterval time.Duration `env:"PIXIVFE_PROXY_CHECK_INTERVAL,overwrite"`
	ProxyCheckTimeout  time.Duration `env:"PIXIVFE_PROXY_CHECK_TIMEOUT,overwrite"`

	// Caching configuration
	CacheEnabled  bool          `env:"PIXIVFE_CACHE_ENABLED,overwrite"`
	CacheSize     int           `env:"PIXIVFE_CACHE_SIZE,overwrite"`
	CacheTTL      time.Duration `env:"PIXIVFE_CACHE_TTL,overwrite"`
	CacheShortTTL time.Duration `env:"PIXIVFE_CACHE_SHORT_TTL,overwrite"`

	// Development options
	InDevelopment        bool   `env:"PIXIVFE_DEV"`
	ResponseSaveLocation string `env:"PIXIVFE_RESPONSE_SAVE_LOCATION,overwrite"`

	// Logging configuration
	LogLevel   string   `env:"PIXIVFE_LOG_LEVEL,overwrite"`
	LogOutputs []string `env:"PIXIVFE_LOG_OUTPUTS,overwrite"`
	LogFormat  string   `env:"PIXIVFE_LOG_FORMAT,overwrite"`
}

// parseRevision extracts RevisionDate, RevisionHash, and IsDirty status from the Revision string
func parseRevision(revision string) (date, hash string, isDirty bool) {
	// Check if Revision is empty
	if revision == "" {
		return unknownRevision, unknownRevision, false
	}

	// Check if Revision is marked as dirty (has uncommitted changes)
	isDirty = strings.HasSuffix(revision, "+dirty")
	if isDirty {
		revision = strings.TrimSuffix(revision, "+dirty")
	}

	// Split Revision into two parts, RevisionDate and RevisionHash
	parts := strings.Split(revision, "-")
	if len(parts) == 2 {
		return parts[0], parts[1], isDirty
	}

	// Return unknown date, full string as hash if format doesn't match date-hash
	return unknownRevision, revision, isDirty
}

func (s *ServerConfig) LoadConfig() error {
	s.Version = "v2.9"
	s.Revision = REVISION

	s.RevisionDate, s.RevisionHash, s.IsDirty = parseRevision(REVISION)

	if REVISION == "" {
		log.Printf("[WARNING] REVISION is not set. Continuing with unknown revision information.\n")
	} else if s.RevisionDate == unknownRevision {
		log.Printf("[WARNING] REVISION format is invalid: %s. Expected format '%s'. Continuing with full revision as hash.\n", REVISION, revisionFormat)
	}

	log.Printf("PixivFE %s, revision %s\n", s.Version, s.Revision)

	s.StartingTime = time.Now().UTC().Format("2006-01-02 15:04")

	// set default values with env:"...,overwrite"
	s.RepoURL = "https://codeberg.org/VnPower/PixivFE"

	s.AcceptLanguage = "en-US,en;q=0.5"
	s.ProxyServer_staging = BuiltinProxyUrl
	s.ProxyCheckEnabled = true
	s.ProxyCheckInterval = 8 * time.Hour
	s.ProxyCheckTimeout = 4 * time.Second
	s.TokenLoadBalancing = "round-robin"
	s.TokenMaxRetries = 5
	s.TokenBaseTimeout = 1000 * time.Millisecond
	s.TokenMaxBackoffTime = 32000 * time.Millisecond

	s.APIMaxRetries = 3
	s.APIBaseTimeout = 500 * time.Millisecond
	s.APIMaxBackoffTime = 8000 * time.Millisecond

	s.CacheEnabled = false
	s.CacheSize = 100
	s.CacheTTL = 60 * time.Minute
	s.CacheShortTTL = 10 * time.Second

	s.ResponseSaveLocation = "/tmp/pixivfe/responses"

	s.LogLevel = "info"
	s.LogOutputs = []string{"stdout"}
	s.LogFormat = "console"

	// load config from from env vars
	if err := envconfig.Process(context.Background(), s); err != nil {
		return err
	}

	// Check if either Port or UnixSocket is set, if not, use defaultPort
	if s.Port == "" && s.UnixSocket == "" {
		s.Port = defaultPort
		log.Printf("[INFO] Neither PIXIVFE_PORT nor PIXIVFE_UNIXSOCKET set. Using default port: %s\n", s.Port)
	}

	// Check for when both Host and UnixSocket are set
	if s.Host != "" && s.UnixSocket != "" {
		log.Printf("[WARNING] Both PIXIVFE_HOST and PIXIVFE_UNIXSOCKET are set. PIXIVFE_HOST will be ignored when using a Unix socket.\n")
	}

	// a check for tokens
	if len(s.Token) < 1 {
		log.Fatalln("PIXIVFE_TOKEN has to be set. Visit https://pixivfe-docs.pages.dev/hosting/hosting-pixivfe for more details.")
		return i18n.Error("PIXIVFE_TOKEN has to be set. Visit https://pixivfe-docs.pages.dev/hosting/hosting-pixivfe for more details")
	}

	// Validate proxy server URL, but skip validation if it already points to the built-in proxy
	if s.ProxyServer_staging == BuiltinProxyUrl {
		proxyURL, _ := url.Parse(BuiltinProxyUrl)
		s.ProxyServer = *proxyURL
		log.Printf("Using built-in proxy server URL: %s\n", BuiltinProxyUrl)
	} else {
		proxyURL, err := utils.ValidateURL(s.ProxyServer_staging, "Proxy server")
		if err != nil {
			log.Printf("[WARNING] Invalid proxy server URL: %v. Falling back to built-in proxy URL.", err)
			proxyURL, _ := url.Parse(BuiltinProxyUrl)
			s.ProxyServer = *proxyURL
			log.Printf("Proxy server set to: %s\n", BuiltinProxyUrl)
		} else {
			s.ProxyServer = *proxyURL
			log.Printf("Proxy server set to: %s\n", proxyURL.String())
		}
	}
	log.Printf("Proxy check interval set to: %v\n", s.ProxyCheckInterval)

	// Validate repo URL
	repoURL, err := utils.ValidateURL(s.RepoURL, "Repo")
	if err != nil {
		log.Printf("[WARNING] Invalid repo URL: %v. Using default repo URL.", err)
		s.RepoURL = "https://codeberg.org/VnPower/PixivFE" // Use a default value
	} else {
		s.RepoURL = repoURL.String()
		log.Printf("Repo URL set to: %s\n", s.RepoURL)
	}

	// Validate TokenLoadBalancing
	switch s.TokenLoadBalancing {
	case "round-robin", "random", "least-recently-used":
		// Valid options
	default:
		log.Printf("[WARNING] Invalid PIXIVFE_TOKEN_LOAD_BALANCING value: %s. Defaulting to 'round-robin'.\n", s.TokenLoadBalancing)
		s.TokenLoadBalancing = "round-robin"
	}

	// Initialize TokenManager
	s.TokenManager = token_manager.NewTokenManager(s.Token, s.TokenMaxRetries, s.TokenBaseTimeout, s.TokenMaxBackoffTime, s.TokenLoadBalancing)
	log.Printf("Token manager initialized with %d tokens\n", len(s.Token))
	log.Printf("Token manager settings: Max retries: %d, Base timeout: %v, Max backoff time: %v\n", s.TokenMaxRetries, s.TokenBaseTimeout, s.TokenMaxBackoffTime)
	log.Printf("Token load balancing method: %s\n", s.TokenLoadBalancing)

	log.Printf("API request backoff settings: Max retries: %d, Base timeout: %v, Max backoff time: %v\n", s.APIMaxRetries, s.APIBaseTimeout, s.APIMaxBackoffTime)

	// Print cache configuration
	if s.CacheEnabled {
		log.Println("API response cache is enabled")
	} else {
		log.Println("API response cache is disabled")
	}

	// Only print ResponseSaveLocation if InDevelopment is set
	if s.InDevelopment {
		log.Printf("Response save location: %s\n", s.ResponseSaveLocation)
	}

	return nil
}

func (s *ServerConfig) GetToken() string {
	token := s.TokenManager.GetToken()
	if token == nil {
		log.Println("[WARNING] All tokens are timed out. Using the first available token.")
		return s.Token[0]
	}
	return token.Value
}
